package org.dei.perla.sys.dbutils;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;


import org.dei.perla.sys.llqruntime.datastructures.Record;
import org.dei.perla.utils.Notifier;
import org.dei.perla.utils.Observer;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

public class RecordBuffer extends Observer {

	private Integer state; 
	private ArrayList<Record> recordBlock1;
	private ArrayList<Record> recordBlock2;
	private DBDumper dbDumper;
	private SupportTimer timer;
	
	private static final int BLOCK2_USED_AS_STORAGE = 1;
	private static final int BLOCK1_USED_AS_STORAGE = 2;
	private static final int IDLE = 3;
	private static final long DEFAULT_TIMEOUT = 1500; //1,5sec timeout

	private Waiter<Pipe<Record>> pRecordWaiter;

	public RecordBuffer(Pipe<Record> parInPipe) {
		pRecordWaiter = new Waiter<Pipe<Record>>();
		pRecordWaiter.start();
		pRecordWaiter.addWaitable(parInPipe.getWaitable());
		this.state = IDLE;
		this.recordBlock1 = new ArrayList<Record>();
		this.recordBlock2 = new ArrayList<Record>();
		timer = new SupportTimer(DEFAULT_TIMEOUT);
		this.startToOberve(timer);
		new WaiterThread().start();
		new Thread(timer).start();
		
	}

	public void setDbDumper(DBDumper dbDumper) {
		this.dbDumper = dbDumper;
	}

	private void changeState(){
		synchronized (state) {
			switch (state.intValue()) {
				case IDLE:
					System.out.println("IDLE");
					this.state = BLOCK1_USED_AS_STORAGE; //e' lo stesso se si parte con l'atro stato
					break;
					
				case BLOCK1_USED_AS_STORAGE:
					System.out.println("BLOCK1_USED_AS_STORAGE");
					synchronized (recordBlock2) {
						if(this.recordBlock2.size() != 0)
							this.state = BLOCK2_USED_AS_STORAGE;
						else{
								this.timer.stopTimer();
								this.state = IDLE;
							}					
					}
					break;
				case BLOCK2_USED_AS_STORAGE:
					System.out.println("BLOCK2_USED_AS_STORAGE");
					synchronized (recordBlock1) {
						if(this.recordBlock1.size() != 0)
							this.state = BLOCK1_USED_AS_STORAGE;
						else{
								this.timer.stopTimer();
								this.state = IDLE;
							}					
					}
					break;

				default:
					break;
			}
		}
	}


	
	private void addToBlock(Record record){
		synchronized (state) {
			switch(state.intValue()){
				case IDLE:{
					this.timer.startTimer();
					changeState();
				}
				case BLOCK1_USED_AS_STORAGE:{
					System.out.println("adding record to block1");
					this.recordBlock1.add(record);	
					break;
				}
				case BLOCK2_USED_AS_STORAGE:{
					System.out.println("adding record to block2");
					this.recordBlock2.add(record);
					break;
				}
				default:{
					return;
				}	
			}
		}
	}

	@Override
	public void whenWokedUp() {
		flushBlocks();
	}

	private void flushBlocks() {
		synchronized (this.state) {
			switch(this.state.intValue()){
				case IDLE:{
					System.out.println("flushBlocks: IDLE");
					break;
				}
				case BLOCK2_USED_AS_STORAGE:{
					System.out.println("flushBlocks: BLOCK2_USED_AS_STORAGE");
					changeState();
					if(dbDumper.process(recordBlock2)){
						recordBlock2.clear();
						
					}
					break;
				}
				case BLOCK1_USED_AS_STORAGE:{
					System.out.println("flushBlocks: BLOCK1_USED_AS_STORAGE");
					changeState();
					if(dbDumper.process(recordBlock1)){
						recordBlock1.clear();
					}
					break;
				}
			}
		}
	}

	private class WaiterThread extends Thread{
		@Override
		public void run() {
			Waitable<Pipe<Record>> waitable;
			Record record;

			while(true) {
				System.out.println("Waiter waitNext..");
				waitable = pRecordWaiter.waitNext();
				record = waitable.getParentComponent().dequeue();
				if (record != null) {
					System.out.println("..Got!");
					addToBlock(record);
				} else {
					pRecordWaiter.removeWaitable(waitable);
					waitable.getParentComponent().stop();
					pRecordWaiter.stop();
					break;
				}
			}
		}	
	}

	private class SupportTimer extends Notifier implements Runnable{
		private static final int RUNNING_STATE = 0;
		private static final int IDLE_STATE = 1;		
		private AtomicInteger stState;
		private long millisSleepTime;

		public SupportTimer(long timeout){
			stState = new AtomicInteger();
			stState.set(IDLE_STATE);
			millisSleepTime = timeout;
		}

		public void startTimer(){
			synchronized (this.stState) {
				this.stState.set(RUNNING_STATE);
				this.stState.notifyAll();
			}
		}
		
		public void stopTimer(){
			synchronized (this.stState) {
				this.stState.set(IDLE_STATE);
				//this.stState.notifyAll();
			}
		}
		
		@Override
		public void run() {
				while(true){
					try {
						switch (this.stState.intValue()) {
							case RUNNING_STATE:{
								Thread.sleep(this.millisSleepTime);
								System.out.println("Timer firing...");
								notifyAllObserver();
							}
							default:{
								System.out.println("Timer suspended");
								synchronized (stState) {
									stState.wait();									
								}
							}
						}
					}catch(InterruptedException e){
						e.printStackTrace();
				}
			}
		}
	}

}


